package com.nys.conci.backend.servicio;

import com.nys.conci.base.ServicioBase;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.util.ByteArrayDataSource;

/**
 * Clase utilitaria para facilitar la conexiÃ³n, creaciÃ³n y envÃ­o de correo de la
 * aplicaciÃ³n, para las notificaciones del sistema.
 *
 * @author wPorras
 * @see Autenticador
 */
@Stateless
@TransactionAttribute(value = TransactionAttributeType.REQUIRED)
public class ControlCorreo extends ServicioBase {

    /**
     * Se almacena el asunto del correo.
     */
    private String asunto_correo;
    /**
     * Almacena el cuerpo del correo.
     */
    private String cuerpo;
    /**
     * Almacena las direcciones de los adjuntos.
     */
    private List<String> adjuntos_direccion;
    /**
     * Almacena los adjuntos asociados al correo en bytes.
     */
    private HashMap<String, byte[]> adjuntos;
    /**
     * Almacena las direcciones de los destinatarios.
     */
    private HashMap<Message.RecipientType, InternetAddress[]> destinatarios;
    /**
     * Almacena la sesiÃ³n para el envÃ­o de correos.
     */
    private Session session;
    /**
     * Contiene el contenido total del correo.
     */
    private MimeMessage mensaje;

    /**
     * Constructor de la clase.<br/> Envia un correo sin documento adjunto.
     *
     * @param asunto Asunto asociado al correo.
     * @param cuerpo Contenido del Documento.
     * @param destinarios Lista de destinatarios.
     */
    public  void initServicioCorreo(String asunto, String cuerpo, HashMap<Message.RecipientType, InternetAddress[]> destinarios) {
        try {
            this.asunto_correo = UTF8.aUTF8(asunto);
            this.cuerpo = UTF8.aUTF8(cuerpo);
            this.destinatarios = destinarios;
            this.adjuntos_direccion = new ArrayList<String>();
            this.adjuntos = new HashMap<String, byte[]>();
            configurarCorreo();
        } catch (Exception ex) {
            Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Constructor de la clase.<br/> Se envÃ­a un correo con la ruta de los
     * documentos adjuntos.
     *
     * @param asunto Asunto del correo.
     * @param cuerpo Contenido del correo.
     * @param destinarios Lista de destinatarios.
     * @param adjuntos Documentos adjuntos.
     */
    public void initServicioCorreo(String asunto, String cuerpo, HashMap<Message.RecipientType, InternetAddress[]> destinarios, List<String> adjuntos) {
        try {
            this.asunto_correo = UTF8.aUTF8(asunto);
            this.cuerpo = UTF8.aUTF8(cuerpo);
            this.destinatarios = destinarios;
            if (adjuntos != null) {
                this.adjuntos_direccion = adjuntos;
            } else {
                this.adjuntos_direccion = new ArrayList<String>();
            }
            this.adjuntos = new HashMap<String, byte[]>();
            configurarCorreo();
        } catch (Exception ex) {
            Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Constructor de la clase.<br/> Se envÃ­a un correo con documento ajuntos en
     * formato binario.
     *
     * @param asunto Asunto del correo.
     * @param cuerpo Cuerpo del correo.
     * @param destinarios Lista de destinatarios.
     * @param adjuntos Documentos adjuntos.
     */
    public  void initServicioCorreo(String asunto, String cuerpo, HashMap<Message.RecipientType, InternetAddress[]> destinarios, HashMap<String, byte[]> adjuntos) {
        try {
            this.asunto_correo = UTF8.aUTF8(asunto);
            this.cuerpo = UTF8.aUTF8(cuerpo);
            this.destinatarios = destinarios;
            if (adjuntos != null) {
                this.adjuntos = adjuntos;
            } else {
                this.adjuntos = new HashMap<String, byte[]>();
            }
            this.adjuntos_direccion = new ArrayList<String>();
            configurarCorreo();
        } catch (Exception ex) {
            Logger.getLogger(ControlCorreo.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Inicializa la sesiÃ³n del correo y envÃ­a el correo con su respectivo
     * contenido.
     */
    public void enviarCorreo() throws Exception {
        if (Configuracion.HABILITADO.estado) {//Utilizado para habilitar o deshabilitar el envÃ­o de correos.
            try {
                Transport t = session.getTransport(Configuracion.PROTOCOLO.valor);
                t.connect(Configuracion.HOST.valor, Configuracion.PUERTO_ALT.numero, Configuracion.CUENTA_ORIGEN.valor, Configuracion.CONTRASENA_ORIGEN.valor);
                t.sendMessage(mensaje, mensaje.getAllRecipients());
                t.close();
            } catch (MessagingException ex) {
                Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
                throw new Exception("Error enviando el correo", ex);
            } catch (Exception ex) {
                Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
                throw new Exception("Error enviando el correo", ex);
            }
        }
    }

    /**
     * Configura la sesion actual del envio de correos.
     */
    private void configurarCorreo() throws Exception {
        boolean flag = false;
        try {
            session = Session.getInstance(propiedadesConexion(), new Autenticador(Configuracion.CUENTA_ORIGEN.valor, Configuracion.CONTRASENA_ORIGEN.valor));
            flag = true;
            mensaje = new MimeMessage(session);
            encabezadoMensaje();
            contenidoMensaje();
        } catch (MessagingException mex) {
            Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, mex);
            if (!flag) {
                throw new Exception("Error al crear la conexion para preparar el correo");
            } else {
                throw new Exception("Error al construir el correo (encabezado o Contenido)");
            }

        }
    }

    /**
     * Construye el encabezado del correo, es decir, indica quien envia el
     * correo, y cuales son todos los destinatarios que se han indicado para el
     * correo.
     *
     * @throws MessagingException
     */
    private void encabezadoMensaje() throws MessagingException {

        //Envia el mensaje
        mensaje.setFrom(new InternetAddress(Configuracion.CUENTA_ORIGEN.valor));

        for (Map.Entry<Message.RecipientType, InternetAddress[]> dest : this.destinatarios.entrySet()) {
            //Destinatario "PARA"
            if (dest.getKey().equals(Message.RecipientType.TO)) {
                mensaje.addRecipients(Message.RecipientType.TO, dest.getValue());
            }//Destinatario "COPIA"
            else if (dest.getKey().equals(Message.RecipientType.CC)) {
                mensaje.addRecipients(Message.RecipientType.CC, dest.getValue());
            } else//Destinatario "COPIA OCULTA"
            {
                mensaje.addRecipients(Message.RecipientType.BCC, dest.getValue());
            }
        }

        mensaje.setSubject(this.asunto_correo);    //Asunto del correo
        mensaje.setSentDate(new Date());    //Fecha de envio
    }

    /**
     * Construye el contenido del correo, es decir, el mensaje que se muestra
     * ademas de los archivos adjuntos que se le agregan.
     *
     * @throws MessagingException
     */
    private void contenidoMensaje() throws MessagingException {

        Multipart contenido_mensaje = new MimeMultipart();

        MimeBodyPart texto = new MimeBodyPart();
        texto.setContent(cuerpo, "text/html;charset=UTF-8");
        contenido_mensaje.addBodyPart(texto);

        try {
            for (String contenido_adjunto : adjuntos_direccion) {
                FileDataSource archivo_adjunto = new FileDataSource(contenido_adjunto);
                MimeBodyPart adjunto = new MimeBodyPart();
                adjunto.setDataHandler(new DataHandler(archivo_adjunto));
                adjunto.setFileName(archivo_adjunto.getName());
                contenido_mensaje.addBodyPart(adjunto);
            }

            for (Map.Entry<String, byte[]> adj : this.adjuntos.entrySet()) {

                MimeBodyPart adjunto = new MimeBodyPart();
                ByteArrayInputStream attach = new ByteArrayInputStream(adj.getValue());
                ByteArrayDataSource ds = new ByteArrayDataSource(attach, "application/pdf");
                adjunto.setDataHandler(new DataHandler(ds));
                adjunto.setFileName(adj.getKey());
                contenido_mensaje.addBodyPart(adjunto);
            }
            mensaje.setContent(contenido_mensaje);
        } catch (Exception e) {
            Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, e);
        }
    }

    /**
     * Inicia los valores para la conexion con el servidor
     *
     * @return todas las propiedades para realizar la conexion
     */
    private Properties propiedadesConexion() {
        Properties propiedades = new Properties();
        propiedades.setProperty("mail.smtp.host", Configuracion.HOST.valor);          //Host del servidor de correo
        propiedades.setProperty("mail.smtp.port", Configuracion.PUERTO.valor);    //Puerto del servidor de correo
        propiedades.setProperty("mail.smtp.user", Configuracion.CUENTA_ORIGEN.valor); //Correo con el cual se va a iniciar la conexion
        propiedades.setProperty("mail.smtp.auth", Configuracion.SMTP_AUTH.valor);     //Indica si el servidor necesita de autentificacion
        propiedades.setProperty("mail.smtp.starttls.enable", Configuracion.SMTP_STARTTLS.valor);  //Habilitar TLS Utilizado en cuenta de Origen Gmail.com
        return propiedades;
    }

    /**
     * Se almacenan todas las configuraciones del correo.
     */
    public enum Configuracion {

        /**
         * Cuenta Origen asociada al correo.
         */
        CUENTA_ORIGEN("controlcitas2013@gmail.com"),
        /**
         * contraseÃ±a de la Cuenta Origen.
         */
        CONTRASENA_ORIGEN("2013controlcita"),
        /**
         * Host asociada a la Cuenta Origen.
         */
        HOST("smtp.gmail.com"),
        /**
         * NÃºmero de puerto.
         */
        PUERTO("587"),//25 587
        /**
         * NÃºmero alternativo.
         */
        PUERTO_ALT(587),//(587),
        /**
         * Almacena el tipo de protocolo del correo.
         */
        PROTOCOLO("smtp"),
        /**
         * Almacena estados de la configuraciÃ³n del correo.
         */
        HABILITADO(true),//Habilita o deshabilitado el envÃ­o de correos.

        /**
         * Almacena la autorizaciÃ³n del SMTP.
         */
        SMTP_AUTH("true"),
        /**
         * Almacena la autorizaciÃ³n de un SMTP STARTTLS Si la cuenta origen es
         * de Gmail por ejemplo
         */
        SMTP_STARTTLS("true"),;//("true"),;
        /**
         * Almacena el valor asociado a la configuraciÃ³n.
         */
        private String valor;
        /**
         * Almacena valores numÃ©ricos definidos en la configuraciÃ³n del correo.
         */
        private int numero;
        /**
         * Almacena valores booleanos.
         */
        private boolean estado;

        Configuracion(String valor) {
            this.valor = valor;
        }

        Configuracion(int numero) {
            this.numero = numero;
        }

        Configuracion(boolean estado) {
            this.estado = estado;
        }
    }
}
